use super::{
    err, err_value, json, ok, throw_err, Base, Client, ECode, Lock, MapSv, Resp, TBase, VBase,
    VMap, Value,
};
use base::{cur_time, tbase::log_stock};
use tracing::{instrument, warn};

pub struct Stock;

impl Base for Stock {}
impl TBase for Stock {}

impl Stock {
    pub async fn do_cmd(&self, para: &Value, cmd: &str, client: &Client, lock: Lock) -> Resp {
        let data = para.ckobj()?;
        let line = data.ck_str("line")?;

        match cmd {
            "fin" => self.do_fin(line, data, client, lock).await,
            "accept" => self.do_accept(line, data, client, lock).await,
            "back" => self.do_back(line, data, client, lock).await,
            "change" => self.do_change(line, data, client, lock).await,
            "defeat" => self.do_defeat(line, data, client, lock).await,
            "order" => self.do_order(line, data, client, lock).await,
            "qa" => self.do_qa(line, data, client, lock).await,
            _ => throw_err!(ECode::NoServe, "no stock serve"),
        }
    }

    #[instrument(skip_all, name = "do_change")]
    async fn do_change(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let sn = data.ck_str("sn")?;
        let tp = data.ck_str("tp")?;
        let opt = data.ck_str("opt")?;
        let num = data.ck_i64("num")?;
        let uname = data.ck_str("uname").unwrap_or("");

        let cmd = json!({ "line": line, "k": sn });

        let body = client.cfgdb(&cmd, &format!("{tp}/get")).await?;
        let body = body.ck_obj("data")?;

        let mut dnum = body.ck_i64("dnum").unwrap_or(0);
        let mut bnum = body.ck_i64("bnum").unwrap_or(0);

        match opt {
            "pick" => {
                dnum -= num;
                bnum += num;

                if dnum < 0 {
                    dnum = 0;
                }
            }
            "out" => {
                bnum -= num;

                if bnum < 0 {
                    bnum = 0;
                }
            }
            "in" => {
                dnum += num;
            }
            _ => throw_err!(ECode::Para, "tp error"),
        }

        let left_num = dnum + bnum;

        if left_num > 0 {
            let cmd = json!({
                "line": line,
                "v": {
                    "sn": sn,
                    "num": left_num,
                    "dnum": dnum,
                    "bnum": bnum,
                }
            });

            client.cfgdb(&cmd, &format!("{tp}/mditem")).await?;
        } else {
            client.cfgdb(&cmd, &format!("{tp}/rm")).await?;
        }

        let mut v = body.clone();
        v.insert("cnum".to_string(), json!(num));
        v.insert("num".to_string(), json!(left_num));
        v.insert("dnum".to_string(), json!(dnum));
        v.insert("bnum".to_string(), json!(bnum));
        v.insert("uname".to_string(), json!(uname));

        let _ = log_stock(line, &format!("lg{tp}"), sn, opt, &v, None, client, "").await;

        if tp == "forge" && opt == "out" {
            let cmd = json!({ "line": line, "rule": "lathe" });

            let gen = client.cfgdb(&cmd, "code/gen").await?;
            let sn = gen.ck_obj("data")?.ck_str("psn")?;

            let cmd = json!({
                "line": line,
                "v": {
                    "sn": sn,
                    "uname": uname,
                    "cnum": num,
                    "bnum": num,
                    "num": num,
                    "dnum": 0,
                    "supply": "01",
                    "tech": data.ck_str("tech")?,
                    "std": data.ck_str("std")?,
                    "tmin": cur_time("%Y-%m-%d %H:%M:%S"),
                }
            });

            client.cfgdb(&cmd, "lathe/add").await?;
            let val = cmd.ck_obj("v")?;

            let _ = log_stock(line, "lglathe", sn, "in", val, None, client, "").await;
        }

        ok!()
    }

    #[instrument(skip_all, name = "do_fin")]
    async fn do_fin(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let sn = data.ck_str("sn")?;
        let tp = data.ck_str("tp")?;
        let uname = match data.k_str("uname") {
            Some(v) => v,
            None => "",
        };

        let tp_pick = format!("{tp}pick");

        warn!("get pick");

        let cmd = json!({ "line": line, "sn": sn });

        let body_pick = client.cfgdb(&cmd, &format!("file/get/{tp_pick}")).await?;
        let body_pick = body_pick.ck_obj("data")?;

        let num = body_pick.ck_i64("num")?;
        let bsn = body_pick.ck_str("bsn")?;

        warn!("update stock data");

        let cmd = json!({ "line": line, "k": bsn });

        let body = client.cfgdb(&cmd, &format!("{tp}/get")).await?;
        let body = body.ck_obj("data")?;

        let dnum = body.ck_i64("dnum")?;

        let mut left_num = dnum - num;
        if left_num < 0 {
            left_num = 0;
        }

        if left_num > 0 {
            let cmd = json!({
                "line": line,
                "v": {
                    "sn": bsn,
                    "dnum": left_num,
                }
            });

            client.cfgdb(&cmd, &format!("{tp}/mditem")).await?;
        } else {
            let cmd = json!({ "line": line, "k": bsn });
            client.cfgdb(&cmd, &format!("{tp}/rm")).await?;
        }

        warn!("rm pick");

        let cmd = json!({ "line": line, "sn": sn });
        client.cfgdb(&cmd, &format!("file/rm/{tp_pick}")).await?;

        warn!("log");

        let mut v = body_pick.clone();
        v.insert("num".to_string(), json!(num));
        v.insert("dnum".to_string(), json!(left_num));
        v.insert("uname".to_string(), json!(uname));

        let _ = log_stock(
            line,
            &format!("lg{tp}"),
            bsn,
            "out",
            &mut v,
            None,
            client,
            "",
        )
        .await;

        ok!()
    }

    #[instrument(skip_all, name = "do_back")]
    async fn do_back(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let sn = data.ck_str("sn")?;
        let tp = data.ck_str("tp")?;
        let num = data.ck_i64("num")?;
        let boxn = data.ck_i64("box")?;

        warn!("get qa data");

        let cmd = json!({
            "line": line,
            "sn": sn,
        });

        let body = client.cfgdb(&cmd, &format!("file/get/qa{tp}")).await?;
        let body = body.ck_obj("data")?;

        let old_box = body.ck_i64("box")?;
        let old_num = body.ck_i64("num")?;

        warn!("update qa data");

        let mut left_num = old_num - num;
        let mut left_box = old_box - boxn;

        if left_num < 0 {
            left_num = 0;
        }

        if left_box < 0 {
            left_box = 0;
        }

        if left_num > 0 {
            let cmd = json!({
                "line": line,
                "sn": sn,
                "v": {
                    "num": left_num,
                    "box": left_box,
                }
            });

            client.cfgdb(&cmd, &format!("file/md/qa{tp}")).await?;
        } else {
            let cmd = json!({ "line": line, "sn": sn });
            client.cfgdb(&cmd, &format!("file/rm/qa{tp}")).await?;
        }

        let cmd = json!({ "line": line, "sn": sn });

        if let Ok(val) = client.cfgdb(&cmd, &format!("file/get/test{tp}")).await {
            let val = val.ck_obj("data")?;
            let old_num = val.ck_i64("num")?;

            let cmd = json!({
                "line": line,
                "sn": sn,
                "v": {
                    "num": old_num + num,
                }
            });

            client.cfgdb(&cmd, &format!("file/md/test{tp}")).await
        } else {
            let mut val = body.clone();
            val.insert("num".to_string(), json!(num));

            val.remove("box");
            val.remove("snum");

            let cmd = json!(val);

            client.cfgdb(&cmd, &format!("file/md/test{tp}")).await
        }
    }

    #[instrument(skip_all, name = "do_accept")]
    async fn do_accept(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let sn = data.ck_str("sn")?;
        let tp = data.ck_str("tp")?;
        let num = data.ck_i64("num")?;
        let boxn = match data.k_i64("box") {
            Some(v) => v,
            None => 0,
        };

        warn!("get qa data");

        let cmd = json!({
            "line": line,
            "sn": sn,
        });

        let body = client.cfgdb(&cmd, &format!("file/get/qa{tp}")).await?;
        let body = body.ck_obj("data")?;

        let old_num = body.ck_i64("num")?;
        let old_box = match body.k_i64("box") {
            Some(v) => v,
            None => 0,
        };

        warn!("update qa data");

        let mut left_num = old_num - num;
        let mut left_box = old_box - boxn;

        if left_num < 0 {
            left_num = 0;
        }

        if left_box < 0 {
            left_box = 0;
        }

        if left_num > 0 {
            let cmd = json!({
                "line": line,
                "sn": sn,
                "v": {
                    "num": left_num,
                    "box": left_box,
                }
            });

            client.cfgdb(&cmd, &format!("file/md/qa{tp}")).await?;
        } else {
            let cmd = json!({
                "line": line,
                "sn": sn,
            });

            client.cfgdb(&cmd, &format!("file/rm/qa{tp}")).await?;
        }

        match tp {
            "oil" | "part" | "forge" | "lathe" => {
                let mut val = body.clone();

                if let Some(uname) = data.k_str("uname") {
                    val.insert("uname".to_string(), json!(uname));
                }

                val.insert("dnum".to_string(), json!(num));
                val.insert("tmin".to_string(), json!(cur_time("%Y-%m-%d %H:%M:%S")));

                let cmd = json!({
                    "line": line,
                    "v": val,
                });

                client.cfgdb(&cmd, &format!("{tp}/add")).await?;

                let _ = log_stock(
                    line,
                    &format!("lg{tp}"),
                    sn,
                    "in",
                    &mut val,
                    None,
                    client,
                    "",
                )
                .await;

                ok!()
            }
            _ => ok!(),
        }
    }

    #[instrument(skip_all, name = "do_defeat")]
    async fn do_defeat(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let sn = data.ck_str("sn")?;
        let ngson = data.ck_str("ngson")?;
        let tp = data.ck_str("tp")?;
        let snum = match data.k_i64("snum") {
            Some(v) => v,
            None => 0,
        };
        let boxn = match data.k_i64("box") {
            Some(v) => v,
            None => 0,
        };
        let mut num = data.ck_i64("num")?;

        warn!("get test data");

        let cmd = json!({
            "line": line,
            "sn": sn,
        });

        let mut body = client.cfgdb(&cmd, &format!("file/get/test{tp}")).await?;
        let body = body.ck_obj_mut("data")?;

        let test_num = body.ck_i64("num")?;
        if num > test_num {
            num = test_num;
        }

        let qa_sn = format!("{}-{}", sn, ngson);

        let cmd = json!({
            "line": line,
            "sn": qa_sn,
        });

        let val = match client.cfgdb(&cmd, &format!("file/get/de{tp}")).await {
            Ok(qa_body) => {
                warn!("update num if qa save");

                let qa_num = qa_body.ck_obj("data")?.ck_i64("num")?;

                json!({
                    "num": qa_num + num,
                    "snum": snum,
                    "box": boxn,
                })
            }
            Err(_) => {
                warn!("create new one if not save");

                body.insert("sn".to_string(), json!(qa_sn));
                body.insert("num".to_string(), json!(num));
                body.insert("snum".to_string(), json!(snum));
                body.insert("box".to_string(), json!(boxn));
                body.insert("ngson".to_string(), json!(ngson));

                match tp {
                    "seal" | "cage" => {
                        let mt = if tp == "seal" { "S" } else { "C" };

                        let qr_spc = self.get_qr_spec(&body, mt, &qa_sn)?;

                        body.insert("qr".to_string(), json!(qr_spc));
                    }
                    _ => (),
                }

                json!(body)
            }
        };

        warn!("update qa data");

        let cmd = json!({
            "line": line,
            "sn": qa_sn,
            "v": val,
        });

        let _ = client.cfgdb(&cmd, &format!("file/md/de{tp}")).await?;

        warn!("update test num");

        let left_num = test_num - num;

        if left_num > 0 {
            let cmd = json!({
                "line": line,
                "sn": sn,
                "v": {
                    "num": left_num,
                    "state": "run",
                }
            });

            let _ = client.cfgdb(&cmd, &format!("file/md/test{tp}")).await?;
        } else {
            let cmd = json!({
                "line": line,
                "sn": sn,
            });

            let _ = client.cfgdb(&cmd, &format!("file/rm/test{tp}")).await?;
        }

        let cmd = json!({
            "line": line,
            "sn": qa_sn,
        });

        client.cfgdb(&cmd, &format!("file/get/de{tp}")).await
    }

    #[instrument(skip_all, name = "do_qa")]
    async fn do_qa(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let sn = data.ck_str("sn")?;
        let qa = data.ck_str("qa")?;
        let tp = data.ck_str("tp")?;
        let snum = match data.k_i64("snum") {
            Some(v) => v,
            None => 0,
        };
        let boxn = match data.k_i64("box") {
            Some(v) => v,
            None => 0,
        };
        let mut num = data.ck_i64("num")?;

        warn!("get test data");

        let cmd = json!({
            "line": line,
            "sn": sn,
        });

        let mut body = client.cfgdb(&cmd, &format!("file/get/test{tp}")).await?;
        let body = body.ck_obj_mut("data")?;

        let test_num = body.ck_i64("num")?;
        if num > test_num {
            num = test_num;
        }

        let qa_sn = format!("{}{}", sn, qa.to_uppercase());

        let cmd = json!({
            "line": line,
            "sn": qa_sn,
        });

        let val = match client.cfgdb(&cmd, &format!("file/get/qa{tp}")).await {
            Ok(qa_body) => {
                warn!("update num if qa save");

                let qa_num = qa_body.ck_obj("data")?.ck_i64("num")?;

                json!({
                    "num": qa_num + num,
                    "snum": snum,
                    "box": boxn,
                })
            }
            Err(_) => {
                warn!("create new one if not save");

                body.insert("sn".to_string(), json!(qa_sn));
                body.insert("num".to_string(), json!(num));
                body.insert("snum".to_string(), json!(snum));
                body.insert("box".to_string(), json!(boxn));

                match tp {
                    "seal" | "cage" => {
                        let mt = if tp == "seal" { "S" } else { "C" };

                        let qr_spc = self.get_qr_spec(&body, mt, &qa_sn)?;

                        body.insert("qr".to_string(), json!(qr_spc));
                    }
                    _ => (),
                }

                json!(body)
            }
        };

        warn!("update qa data");

        let cmd = json!({
            "line": line,
            "sn": qa_sn,
            "v": val,
        });

        let _ = client.cfgdb(&cmd, &format!("file/md/qa{tp}")).await?;

        warn!("update test num");

        let left_num = test_num - num;

        if left_num > 0 {
            let cmd = json!({
                "line": line,
                "sn": sn,
                "v": {
                    "num": left_num,
                    "state": "run",
                }
            });

            let _ = client.cfgdb(&cmd, &format!("file/md/test{tp}")).await?;
        } else {
            let cmd = json!({
                "line": line,
                "sn": sn,
            });

            let _ = client.cfgdb(&cmd, &format!("file/rm/test{tp}")).await?;
        }

        let cmd = json!({
            "line": line,
            "sn": qa_sn,
        });

        client.cfgdb(&cmd, &format!("file/get/qa{tp}")).await
    }

    #[instrument(skip_all, name = "do_order")]
    async fn do_order(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let order_sn = data.ck_str("sn")?;
        let pos = data.ck_str("pos")?;

        warn!("get order");

        let cmd = json!({
            "line": line,
            "k": order_sn,
        });

        let body = client.cfgdb(&cmd, "order/get").await?;
        let body = body.ck_obj("data")?;

        let qa_sn = body.ck_str("bsn")?;
        let usn = body.ck_str("usn")?;
        let mt = body.ck_str("mt")?;
        let num = body.ck_i64("num")?;

        let tp = self.get_mt_type(mt)?;

        warn!("get qa data");

        let cmd = json!({
            "line": line,
            "sn": qa_sn,
        });

        let mut qa_body = client.cfgdb(&cmd, &format!("file/get/qa{tp}")).await?;
        let qa_body = qa_body.ck_obj_mut("data")?;

        let qa_num = qa_body.ck_i64("num")?;

        warn!("deal with stock");

        let cmd = json!({
            "line": line,
            "k": qa_sn,
        });

        match client.cfgdb(&cmd, &format!("{tp}/get")).await {
            Ok(mt_body) => {
                let mt_body = mt_body.ck_obj("data")?;

                warn!("update num if stock save");

                let mt_dnum = mt_body.ck_i64("dnum")?;
                let mt_num = mt_body.ck_i64("num")?;

                let cmd = json!({
                    "line": line,
                    "v": {
                        "sn": qa_sn,
                        "dnum": mt_dnum + num,
                        "num": mt_num + num,
                        "pos": pos,
                        "tmin": base::cur_time("%Y-%m-%d %H:%M:%S"),
                    }
                });

                let _ = client.cfgdb(&cmd, &format!("{tp}/mditem")).await?;
            }
            Err(_) => {
                warn!("create new one if not save");

                qa_body.insert("pos".to_string(), json!(pos));
                qa_body.insert("dnum".to_string(), json!(num));
                qa_body.insert("num".to_string(), json!(num));
                qa_body.insert("state".to_string(), json!("ok"));
                qa_body.insert(
                    "tmin".to_string(),
                    json!(base::cur_time("%Y-%m-%d %H:%M:%S")),
                );

                let cmd = json!({
                    "line": line,
                    "v": qa_body,
                });

                let _ = client.cfgdb(&cmd, &format!("{tp}/add")).await?;
            }
        };

        warn!("remove order");

        let cmd = json!({
            "line": line,
            "k": order_sn,
        });

        let _ = client.cfgdb(&cmd, "order/rm").await?;

        warn!("update qa data");

        let left_num = qa_num - num;

        if left_num > 0 {
            let cmd = json!({
                "line": line,
                "sn": qa_sn,
                "v": {
                    "num": left_num,
                }
            });

            let _ = client.cfgdb(&cmd, &format!("file/md/qa{tp}")).await?;
        } else {
            let cmd = json!({
                "line": line,
                "sn": qa_sn,
            });

            let _ = client.cfgdb(&cmd, &format!("file/rm/qa{tp}")).await?;
        }

        let mut his = body.clone();
        let k = json!({ "usn": usn });

        let _ = log_stock(
            line,
            "hisorder",
            "mes",
            "mes",
            &mut his,
            Some(k),
            client,
            "",
        )
        .await;

        let cmd = json!({
            "line": line,
            "k": qa_sn,
        });

        client.cfgdb(&cmd, &format!("{tp}/get")).await
    }

    fn get_qr_spec(&self, data: &MapSv, tp: &str, sn: &str) -> Result<String, Value> {
        let tech = data.ck_str("tech")?;
        let ball = data.ck_str("ball")?;
        let supply = data.ck_str("supply")?;
        let boxn = data.ck_i64("box")?;
        let num = data.ck_i64("num")?;
        let snum = data.ck_i64("snum")?;

        let ret = match tp {
            "C" => {
                let stuffnum = match data.k_str("stuffnum") {
                    Some(v) => v,
                    None => "",
                };

                format!("{tp}#{supply}#{sn}#{tech}*{ball}*{stuffnum}#{boxn}#{snum}#{num}")
            }
            "S" => {
                let face = data.ck_str("face")?;

                format!("{tp}#{supply}#{sn}#{tech}*{ball}*{face}#{boxn}#{snum}#{num}")
            }
            _ => {
                throw_err!(ECode::Para, "mt type error");
            }
        };

        Ok(ret)
    }
}
